/*
Copyright (c) 2011, Carsten Kolve.
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

* Neither the names of PointAttribViz
nor the names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

// OpenGL
#include <GL/glew.h>
#include <GL/gl.h>

// Houdini
#include <RE/RE_Render.h>
#include <RE/RE_Shader.h>
#include <UT/UT_Interrupt.h>
#include <UT/UT_Version.h>
#include <UT/UT_Ramp.h>

// PointAttribViz
#include "GR_PointAttribVizHook.h"


#define debugme 1 

#define SCALEMODE_DEFAULT 0
#define SCALEMODE_VECX 1
#define SCALEMODE_VECY 2
#define SCALEMODE_VECZ 3
#define SCALEMODE_VECLENGTH 4

#define COLORMODE_DEFAULT 0
#define COLORMODE_COLORATTRIB 1
#define COLORMODE_RAMPVECX 2
#define COLORMODE_RAMPVECY 3
#define COLORMODE_RAMPVECZ 4
#define COLORMODE_RAMPVECLENGTH 5


///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ctor
GR_PointAttribVizHook::GR_PointAttribVizHook()
{
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// dtor
GR_PointAttribVizHook::~GR_PointAttribVizHook()
{
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Tell Houdini to only render SOP_PointAttribViz details with this render hook.
int GR_PointAttribVizHook::getWireMask( GU_Detail *gdp, const GR_DisplayOption *dopt ) const
{
	#ifdef debugme
	std::cout << "GR_PointAttribVizHook::getWireMask" << std::endl;
	#endif
	if ( gdp->attribs().find("SOP_PointAttribViz", GB_ATTRIB_MIXED) )
	{
		return 0;
	}	
	else
	{
    		return GEOPRIMALL;
	}
}

int GR_PointAttribVizHook::getShadedMask( GU_Detail *gdp, const GR_DisplayOption *dopt	) const
{
	#ifdef debugme
	std::cout << "GR_PointAttribVizHook::getShadedMask" << std::endl;
	#endif
	return getWireMask( gdp, dopt );
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Render SOP_PointAttribViz wireframe
void GR_PointAttribVizHook::renderWire(  GU_Detail *gdp,
					RE_Render &ren,
					const GR_AttribOffset &ptinfo,
					const GR_DisplayOption *dopt,
					float lod,
					const GU_PrimGroupClosure *hidden_geometry )
{
	#ifdef debugme
	std::cout << "GR_PointAttribVizHook::renderWire" << std::endl;
	#endif
	if ( gdp->attribs().find("SOP_PointAttribViz", GB_ATTRIB_MIXED) )
	{
		drawPointAttribData( gdp, ren, dopt );
	}
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Render SOP_PointAttribViz shaded
void GR_PointAttribVizHook::renderShaded(GU_Detail *gdp,
					RE_Render &ren,
					const GR_AttribOffset &ptinfo,
					const GR_DisplayOption *dopt,
					float lod,
					const GU_PrimGroupClosure *hidden_geometry )
{
	if ( gdp->attribs().find("CrowdTools::SOP_PointAttribViz", GB_ATTRIB_MIXED) )
	{
		#ifdef debugme
		std::cout << "GR_PointAttribVizHook::renderShaded" << std::endl;
		#endif

		ren.pushShader();
		ren.bindShader(NULL);
		drawPointAttribData( gdp, ren, dopt, true  );
		ren.popShader();
	}
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int GR_PointAttribVizHook::getGeoPointAttribHandle(GU_Detail * gdp,UT_String &attribName, UT_String &attribStr, GEO_AttributeHandle &attribHandle)
{
	bool validAttribHandle = true;

	validAttribHandle = (attribStr.isstring() && (attribStr.length()>0));
	if (validAttribHandle)
	{
		attribStr.forceValidVariableName();
		attribHandle = gdp->getPointAttribute(attribStr);
		validAttribHandle = attribHandle.isAttributeValid();
		if (validAttribHandle) 
		{
			useAttrib =  (attribHandle.isFloatOrVectorAttribute() || attribHandle.isP());
		}
		else
		{
			UTString errorStr = " attrib: '";
			errorStr += attribStr;
			raisePointAttribVizError(attribName, errorStr,"' is not a valid float or vector attribute!");
		}
	}
	else
	{
		UTString errorStr = " attrib: '";
		errorStr += attribStr;
		raisePointAttribVizError(attribName, errorStr,"' is empty!");
	}

	return validAttribHandle;	
}


// validate we have a valid fit range, do this in a separate function to save on overhead
bool GR_PointAttribVizHook::checkFitRange(const float oldMin, const float oldMax,const float newMin, const float newMax, UT_String &errorStr)
{
	bool ok = true;

	if (oldMin >= oldMax)
	{	
		errorStr = ("oldMin >= oldMax, oldMin must be < oldMax!");
		ok = false;		
	}
	else
	{
		if (newMin > newMax)
		{
			errorStr = (" newMin > newMax, newMin must be <= newMax!");
			ok = false;
		}
	}

	return ok;
}

// Returns a number between newMin and newMax that is
// relative to num in the range between oldMin and oldMax
// e.g. fit(3,1,4,5,20)=15
// we are assuming the fit range has been validated with check fit range!
float GR_PointAttribVizHook::fit(const float num,const float oldMin,const float oldMax,const float newMin,const float newMax)
{
	return ((num - oldMin)/(oldMax - oldMin))*(newMax - newMin)+newMin; 
}

float GR_PointAttribVizHook::fitFaster(const float num,const float oldMin,const float oldRange,const float newMin,const float newRange)
{
	return ( (num - oldMin)/oldRange) * newRange + newMin; 
}

// range ratio = newRange / oldRange; ratio = (newMax - newMin)/(oldMax - oldMin);
float GR_PointAttribVizHook::fitEvenFaster(const float num, const float oldMin, float newMin,const float rangeRatio)
{
	return (num - oldMin) * rangeRatio+newMin; 
}


	
void GR_PointAttribVizHook::raisePointAttribVizError(const UT_String &strA, const UT_String &strB, const UT_String &strC)
{
	UT_String errorStr = "ERROR: pointAttribVizSop '";
	errorStr += pointAttribViz_sop->getName();
	errorStr += "', ";
	errorStr += strA;
	errorStr += strB;
	errorStr += strC;
	std::cout << errorStr << std::endl;
}


void GR_PointAttribVizHook::getPointPosAttribValue(GEO_Point *point, GEO_AttributeHandle &attribHandle, UT_Vector4 &pos )
{
	if (attribHandle.isP())
	{
		pos = point->getPos(); 
	}
	else
	{
		attribHandle.setElement(point);
		pos = attribHandle.getV4();
	}
}

bool GR_PointAttribVizHook::pointAttribHasRequiredEntries(GEO_AttributeHandle &attribHandle, UTString &attribName,  UTString attribStr, const int requiredEntries)
{
	bool result = (attribHandle.entries() >= requiredEntries);	
	if (!result)
	{
		UTString errorStr = attribName;
		errorStr += " attrib: '";
		errorStr += attribStr;
		errorStr += "' has '";
		errorStr += attribHandle.entries();
 		errorStr += "' entries, but at least '";
		errorStr += requiredEntries;
		raisePointAttribVizError(attribName, errorStr,"' are required!");
	} 

	return result;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void GR_PointAttribVizHook::drawPointAttribData( GU_Detail * gdp, RE_Render &ren, const GR_DisplayOption *dopt, bool shaded)
{
	// cast detail data back to our pass struct
	GB_AttributeRef attrOffset = gdp->attribs().getOffset( "SOP_PointAttribViz", GB_ATTRIB_MIXED );
	SOP_PointAttribVizPassStruct *pointAttribViz_ptr = gdp->attribs().castAttribData<SOP_PointAttribVizPassStruct>(attrOffset);
	if ( !pointAttribViz_ptr )
		return;
	
	pointAttribViz_sop = pointAttribViz_ptr->ptr();
	
	#ifdef debugme
	std::cout << "GR_PointAttribVizHook::pointAttribViz_sop " << std::endl;
	std::cout << "                       name: " << pointAttribViz_sop->getName() << std::endl;
	#endif

	// get attributes from pointattribvizsop at specified time
	// for now not animated
	float time = 0;

	// GROUP
	UT_String myGroupStr;
	pointAttribViz_sop->GROUP(myGroupStr, time);

	//POSAATTRIB
	UT_String myPosAAttribStr;
	pointAttribViz_sop->POSAATTRIB(myPosAAttribStr, time);
	GEO_AttributeHandle posAAttribHandle;

	bool validPosAAttribHandle = getGeoPointAttribHandle(gdp, myPosAAttribStr, posAAttribHandle);
	validPosAAttribHandle = pointAttribHasRequiredEntries(posAAttribHandle, "PosA", myPosAAttribStr,3);



	// scale 	
 	float myScale = pointAttribViz_sop->SCALEDEF(time);
	UT_String myScaleAttribStr;
	pointAttribViz_sop->SCALEATTRIB(myScaleAttribStr, time);
	GEO_AttributeHandle scaleAttribHandle;
	UT_Vector2 myScaleRangeOld = pointAttribViz_sop->SCALEATTRIBRANGE(time);
	UT_Vector2 myScaleRangeNew = pointAttribViz_sop->SCALEDISPLAYRANGE(time);
	float scaleRangeRatio;

	int myScaleMode = pointAttribViz_sop->SCALEMODE(time);
	int scaleAttribValid = getGeoPointAttribHandle(gdp, 
						myScaleMode,
						myScaleAttribStr,
						scaleAttribHandle);
	
	// scale mode >= 2 need to check we have a vector or float attribute??
	if (scaleAttribValid)
	{
		if (myScaleMode > SCALEMODE_VECX)
		{
			scaleAttribValid = ( scaleAttribHandle.entries() == 3 );
			if (!scaleAttribValid)
			{
				myScaleMode = SCALEMODE_DEFAULT;
				raisePointAttribVizError("ScaleAttrib '",myScaleAttribStr,"' is not a valid vector attribute, using default instead!");
			}
		}
	}

	if (myScaleMode && scaleAttribValid)
	{
		UT_String errorStr;
		if (checkFitRange(myScaleRangeOld[0], myScaleRangeOld[1], myScaleRangeNew[0], myScaleRangeNew[1], errorStr))
		{
			scaleRangeRatio = (myScaleRangeNew[1] - myScaleRangeNew[0])/(myScaleRangeOld[1] - myScaleRangeOld[0]);
			std::cout << "ratio " << scaleRangeRatio << std::endl;	
		}
		else
		{
			raisePointAttribVizError("ScaleRange '",errorStr,"!");
			return;
		}
	}


	// color
	UT_Color myColor = pointAttribViz_sop->COLORDEF(time);
	GEO_AttributeHandle colorAttribHandle;
	UT_String myColorAttribStr;
	UT_Vector2 myColorRampRange;
	float colorRampRangeVal;
	pointAttribViz_sop->COLORATTRIB(myColorAttribStr, time);

	int myColorMode = pointAttribViz_sop->COLORMODE(time);
	bool useColorRamp = (myColorMode > 1 );
	UT_Ramp myColorRamp;

	if (myColorMode > COLORMODE_DEFAULT)
	{
		int colorAttribValid = getGeoPointAttribHandle(gdp, 
						myColorMode,
						myColorAttribStr,
						colorAttribHandle);

		if (colorAttribValid)
		{
			if (myColorMode > COLORMODE_RAMPVECX)
			{
				if (colorAttribHandle.entries() != 3)
				{
					myColorMode = COLORMODE_DEFAULT;
					raisePointAttribVizError("ColorAttrib '",myColorAttribStr,"' is not a valid vector attribute, using default instead!");
				}
			}
		}
		else
		{
			myColorMode = COLORMODE_DEFAULT;	
			raisePointAttribVizError("ColorAttrib '",myColorAttribStr,"' is not a valid attribute, using default instead!");
		}


		if (useColorRamp)
		{
			myColorRampRange = pointAttribViz_sop->COLORRAMPRANGE(time);
			colorRampRangeVal = myColorRampRange[1] - myColorRampRange[2];
			if (colorRampRangeVal == 0.0)
			{
				raisePointAttribVizError("ColorRampRange invalid! Min needs to be < Max,"," using default instead!","");
				myColorMode = COLORMODE_DEFAULT;
			}
			pointAttribViz_sop->COLORRAMP(myColorRamp, time); 
		}
	}

	
	// offset
	UT_Vector3 myOffset = pointAttribViz_sop->OFFSET(time);


	//
	glPushAttrib(GL_ALL_ATTRIB_BITS);
	
	// for now just draw some points
	int i, npoint;
	GEO_Point 	*point;
	UT_Vector4	 posA;
	UT_Vector3	 color; myColor.getValue(color[0],color[1],color[2]);
	float	colorRampLookup = 0;
	float	rampValues[4];
	float	scale = myScale;
	
	// initialize with default values
	ren.pushColor(myColor);
	ren.pushPointSize(myScale);

	npoint = gdp->points().entries();
	

	#ifdef debugme
	std::cout << "                    npoint: " << npoint << std::endl;
	std::cout << "                    scaleMode: " << myScaleMode << std::endl;
	std::cout << "                    colorMode: " << myColorMode << " ramp " << useColorRamp << std::endl;

	#endif

	const GB_PointGroup *myGroup = pointAttribViz_sop->parsePointGroups(myGroupStr, gdp);
	if (pointAttribViz_sop->error() < UT_ERROR_ABORT) 
	{       	
		FOR_ALL_OPT_GROUP_POINTS(gdp, myGroup, point)
       		{
			// get posA
			getPointPosAttribValue(point, posAAttribHandle, !posAAttribIsNotP, posA );

			
			// scale
			if (myScaleMode > SCALEMODE_DEFAULT)
			{
				scaleAttribHandle.setElement(point);

				switch (myScaleMode)
				{
					case SCALEMODE_VECX: { scale = scaleAttribHandle.getF(); break; } 
					case SCALEMODE_VECY: { scale = scaleAttribHandle.getV3()[1]; break; } 
					case SCALEMODE_VECZ: { scale = scaleAttribHandle.getV3()[2]; break; } 
					case SCALEMODE_VECLENGTH: { scale = scaleAttribHandle.getV3().length(); break; } 
					default: {raisePointAttribVizError("SCALEMODE invalid: this should never happen! ","","");}
				}
	
				// remap the scale
				scale = (scale - myScaleRangeOld[0]) * scaleRangeRatio + myScaleRangeNew[0];
			}

			// color
			//getVizColor(colorAttribHandle, myColorMode, useColorRamp, color); 

			if (myColorMode > COLORMODE_DEFAULT)
			{
				colorAttribHandle.setElement(point);			

				switch (myColorMode)
				{
					case COLORMODE_COLORATTRIB: { color = colorAttribHandle.getV3(); break; }
					case COLORMODE_RAMPVECX: { colorRampLookup = colorAttribHandle.getF(); break; } 
					case COLORMODE_RAMPVECY: { colorRampLookup = colorAttribHandle.getV3()[1]; break; } 
					case COLORMODE_RAMPVECZ: { colorRampLookup = colorAttribHandle.getV3()[2]; break;} 
					case COLORMODE_RAMPVECLENGTH: { colorRampLookup = colorAttribHandle.getV3().length(); break; } 
					default: {raisePointAttribVizError("COLORMODE invalid: this should never happen! ","","");}
				}

				if (useColorRamp)
				{	
					// remap the lookup value to 0,1 range
					colorRampLookup =  (colorRampLookup - myColorRampRange[0])/colorRampRangeVal; 
					myColorRamp.rampLookup(colorRampLookup, rampValues); 
					color[0] = rampValues[0];
					color[1] = rampValues[1];
					color[2] = rampValues[2];
				}
			}
			
			// draw the point		
			std::cout << "posA " << posA << "\tcol " << color << "\tscale " << scale << " * " <<std::endl;	
			
			ren.setColor(color[0],color[1],color[2], 1.0);	

			ren.setPointSize(scale);	
			ren.beginPoint();

			posA = posA + myOffset;
			ren.vertex3DW(posA[0], posA[1], posA[2]);
			
			ren.endPoint();
		}
	}

    	// pop gl states
	ren.popPointSize();
	ren.popColor();

	glPopAttrib();


}

